
#include "NBGUI.h"
#include <fstream>

#include "Button.h"
#include "Canvas.h"
#include "Image.h"
#include "ProgressBar.h"
#include "KeyBoard.h"
#include "Mouse.h"
#include "textBox.h"
#include "CheckBox.h"
#include "Menu.h"
#include "MenuItem.h"
#include "Tab.h"
#include "Subtab.h"

using namespace NBGUI;
using namespace tinyxml2;



NBGUI::GuiCore::GuiCore( RendererBase* renderer ): drawer(new GUIDrawer(renderer, &callBackFuncMap))
{
	eleTypeMap["canvas"] = CANVAS;
	eleTypeMap["button"] = BUTTON;
	eleTypeMap["img"] = IMAGE;
	eleTypeMap["progressBar"] = PROGRESSBAR;
	eleTypeMap["textbox"] = TEXTBOX;
	eleTypeMap["scrollbar"] = SCROLLBAR;
	eleTypeMap["checkbox"] = CHECKBOX;
	eleTypeMap["menu"] = MENU;
	eleTypeMap["menuitem"] = MENUITEM;
	eleTypeMap["tab"] = TAB;
	eleTypeMap["subtab"] = SUBTAB;
	eleTypeMap["handler"] = HANDLER;
	eleTypeMap["info"] = INFO;
 	 
	initInput();

	drawer->font = new Font("Resource\\arial.ttf",renderer,16, renderer->screenWidth, renderer->screenHeight);

	//vertices = new std::vector<Vertex>();
	//indices = new std::vector<unsigned int>();
}

NBGUI::GuiCore::~GuiCore()
{
	
	std::for_each(begin(inputDevice),end(inputDevice),[&](Input* input){
		delete input;
	});
	
	//delete rootNode;
	delete drawer;
	
	//delete font;
}


void NBGUI::GuiCore::initInput()
{
	//create input system & bind the camera to the input
	Mouse* mouse = new Mouse();
	mouse->init(drawer->pRenderer->getHwnd());
	KeyBoard* keyboard = new KeyBoard(); 


	inputDevice.push_back(mouse);
	inputDevice.push_back(keyboard);
}

Layout* NBGUI::GuiCore::loadLayout( const char* fileName )
{
	try
	{
		Layout* layout = drawer->playout =new Layout(fileName);
		 

		tinyxml2::XMLDocument doc;
		doc.LoadFile( "testLayout.xml" );
		XMLElement* theElement = doc.FirstChildElement( "canvas" );
		if (theElement == nullptr)
		{
			throw GUIException(InvalidLayout,L"The layout file is broken. It should contain canvas as root element.");
		}
		else if(theElement->FirstChildElement() != nullptr)
		{
			layout->root = buildElement(theElement);

			//buildElementHandlers(layout->root);

			std::for_each(begin(inputDevice),end(inputDevice),[&](Input* input){
				input->registerListener(layout->root);
			});
			 
			return layout;
		}
		else
		{
			return layout;
		}
		
	}
	catch (GUIException& e)
	{
		e.popErrorWindow();
		return nullptr;
	}
	 
	
}

ElementNode* NBGUI::GuiCore::buildElement(XMLElement* xmlele)
{
	

	//build GUI element from xml 

	const char* name = xmlele->Attribute("name");
	int x = xmlele->IntAttribute("x");
	int y = xmlele->IntAttribute("y");
	int w = xmlele->IntAttribute("w");
	int h = xmlele->IntAttribute("h");
	if (!name)
	{
		throw GUIException(InvalidElementName,L"Invalid Layout Element name.");
	}

	const char* eleText = xmlele->Attribute("text");

	const char* elementNodeType = xmlele->Name();//different from the attribute name

	if (!elementNodeType)
	{
		throw GUIException(InvalidElementType,L"Invalid Layout Element Type.");
	}
	else
	{
		GUIELEMENT tp = castNodeType(elementNodeType);
		ElementNode* newElement = nullptr ;
		switch(tp)
		{
			case CANVAS:newElement = new Canvas(drawer, name, (eleText ? eleText : ""),tp);
				break;
			case BUTTON:newElement = new Button(drawer, name, (eleText ? eleText : ""),tp,Rect(x,y,w,h));
				break;
			case IMAGE:newElement = new Image(drawer, name, (eleText ? eleText : ""), tp,Rect(x,y,w,h),xmlele->Attribute("resource"));
					   static_cast<Image*>(newElement)->loadImg(drawer->pRenderer);
				break;
			case PROGRESSBAR: 
				{
					newElement = new ProgressBar(drawer, name, (eleText ? eleText : ""),tp,Rect(x,y,w,h),xmlele->FloatAttribute("progress"));

					break;
				}
			case TEXTBOX: newElement = new TextBox(drawer, name, (eleText ? eleText : ""),tp,Rect(x,y,w,h));
				break;
			case CHECKBOX: newElement = new CheckBox(drawer, name, (eleText ? eleText : ""),tp,Rect(x,y,w,h));
				break;
			case MENU:newElement = new Menu(drawer, name, (eleText ? eleText : ""),tp,Rect(x,y,w,h));
				break;
			case MENUITEM: newElement = new MenuItem(drawer, name, (eleText ? eleText : ""),tp,Rect(x,y,w,h),xmlele->BoolAttribute("click"));
				break;
			case TAB: 
					newElement = new Tab(drawer, name, (eleText ? eleText : ""),xmlele->IntAttribute("sizeW"),xmlele->IntAttribute("sizeH"), tp,Rect(x,y,w,h) );

					// TODO testing
					//std::vector<info> test;
					//info anInfo;
					//anInfo.i = 1;
					//test.push_back( anInfo );
					//caller.call( newElement, "doSomething", test, std::string("aaa") );

					break;																			    
			 
			case SUBTAB: 
				newElement = new Subtab(drawer, name, (eleText ? eleText : ""), xmlele->IntAttribute("id"),tp,Rect(x,y,w,h) );
				break;
		}

		int handlerNum = xmlele->IntAttribute("handlerNum");
		XMLElement* handleElement = xmlele->FirstChildElement();
		for(int i = 0; i < handlerNum; ++i,handleElement = handleElement->NextSiblingElement())
		{
			
			Handler h;
			const char* callBackFuncName = handleElement->Attribute("callback");
			int paraNum;
			if(callBackFuncName == nullptr)
			{
				h.name=  handleElement->Attribute("name");
				h.funcName = handleElement->Attribute("func");
				h.eventType = handleElement->IntAttribute("type");
					
			}
			else
			{
				h.handleType = -1;//callback mark value, default is 0
				h.eventType = handleElement->IntAttribute("type");
				h.funcName = callBackFuncName;
			}

			paraNum = handleElement->IntAttribute("paraNum");
			for (int j = 0; j < paraNum; ++j)
			{
				XMLElement* infoElement = handleElement->FirstChildElement();
				int valType = infoElement->IntAttribute("valType");
				info para;
				switch(valType)
				{
				case 0: para.i = infoElement->IntAttribute("val");break;
				case 1: para.f = infoElement->FloatAttribute("val");break;
				case 2: para.str = (char*)infoElement->Attribute("val");break;

				}
				h.parameters.push_back(para);
			}
			 
			newElement->add(h);
		}
		


		if(newElement && !xmlele->NoChildren())
		{
			xmlele = xmlele->FirstChildElement();
			for (;;xmlele = xmlele->NextSiblingElement())
			{
				
				if(xmlele)
				{
					newElement->attachElementNode( buildElement(xmlele));
				}
				else
				{
					break;
				}
			}
		}
		return newElement;
	}

	
}

void NBGUI::GuiCore::update(int frameRate)
{
	drawer->pRenderer->beforeFrame();

	drawer->pRenderer->turnOffDepth();

	std::for_each(begin(inputDevice),end(inputDevice),[&](Input* input){
		input->update();//create events
		input->handleAllEvents();
	});
	//draw
	
	
	drawer->playout->root->draw();

	//drawer->pRenderer->swapBuffer(true);
}

NBGUI::GUIELEMENT NBGUI::GuiCore::castNodeType( const char* tpStr )
{
	if (eleTypeMap.find(tpStr) == eleTypeMap.end())
	{
		throw GUIException(UnknownElementType,L"Unkonwn element type!",true);
		return NONE;
	}
	else
	{
		return eleTypeMap[tpStr];
	}
}

void NBGUI::GuiCore::initDrawing()//
{
	try
	{
		if(drawer->playout->root == nullptr)
		{
			throw GUIException(InvalidLayout,L"The layout file is empty.");
		}
		//recur the tree

		std::vector<Vertex>* vertices = drawer->playout->vertices;
		std::vector<unsigned int>* indices= drawer->playout->indices;
		transformCoordnates(drawer->playout->root);//build the vertices

		if(vertices->size() > 0 && indices->size() > 0)
		{
			drawer->vboId = drawer->pRenderer->createVBO(&vertices->front(),vertices->size(),sizeof(Vertex),0,"PTC");
			drawer->iboId = drawer->pRenderer->createIBO(&indices->front(),indices->size());
		}

		drawer->font->initDrawing();
	}
	catch (GUIException& e)
	{
		e.popErrorWindow();
	}
	
}

void NBGUI::GuiCore::transformCoordnates( ElementNode* element )
{

	if(element->getElementType() >= BUTTON)
	{
		element->buildVI(drawer->playout->vertices,drawer->playout->indices, drawer->pRenderer->screenWidth, drawer->pRenderer->screenHeight);	
	}	

	std::vector<ElementNode*> children = element->getChildren();

	for (auto it = children.begin(); it != end(children); ++it)
	{
		transformCoordnates(*it);
	}
}

void NBGUI::GuiCore::processMessage( MSG msg )
{
	std::for_each(begin(inputDevice),end(inputDevice),[&](Input* input){
		input->processMessages(msg.message,msg.wParam,msg.lParam);
	});


}

ElementNode* NBGUI::GuiCore::buildElementHandlers( ElementNode* element )
{
	size_t num = element->handlerList.size();
	for (size_t i = 0;i < num; ++i) 
	{
		ElementNode* handlerNode = element->findNode(drawer->playout->root,element->handlerList[i].name);
		//element->handlerList[i].setFunc( handlerNode->getFunction(element->handlerList[i].funcName) );
		//handlerNode->getFunction(element->handlerList[i].funcName);
	}

	if (element->hasChild())
	{
		std::vector<ElementNode*>& children = element->getChildren();
		for (size_t i = 0; i< children.size(); ++i)
		{
			buildElementHandlers(children[i]);
		}
	}
	return nullptr;
}

void NBGUI::GuiCore::addCallBackFunc( std::string& callBackFuncName,const std::function< void(std::vector<info>& i) >& f)
{
	callBackFuncMap[callBackFuncName] = f;
}


